package org.codehaus.activemq.service.impl;

import com.sleepycat.util.FastInputStream;
import com.sleepycat.util.FastOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import javax.jms.JMSException;
import org.codehaus.activemq.service.QueueList;
import org.codehaus.activemq.service.QueueListEntry;
import org.codehaus.activemq.util.JMSExceptionHelper;

public abstract class QueueListSupport
  implements QueueList
{
  protected static final Long HEAD_KEY = new Long(0L);

  public Object getFirst()
    throws JMSException
  {
    try
    {
      Long key = getHeader().headKey;
      if (key != null) {
        Node node = getNode(key);
        if (node != null) {
          return node.getElement();
        }
      }
      return null;
    } catch (IOException e) {
    	 throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
    }
   
  }

  public Object getLast() throws JMSException
  {
    try {
      Long key = getHeader().tailKey;
      if (key != null) {
        Node node = getNode(key);
        if (node != null) {
          return node.getElement();
        }
      }
      return null;
    } catch (IOException e) {
    	 throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
    }
   
  }

  public Object removeFirst() throws JMSException
  {
    try {
      Header header = getHeader();
      Long key = header.headKey;
      if (key != null) {
        Node node = getNode(key);
        if (node != null) {
          doRemoveNode(node);
          header.headKey = node.nextKey;
          header.size -= 1;
          updateHeader(header);
          return node.getElement();
        }
      }
      return null;
    } catch (IOException e) {
    	throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
    }
    
  }

  public Object removeLast() throws JMSException
  {
    try {
      Header header = getHeader();
      Long key = header.tailKey;
      if (key != null) {
        Node node = getNode(key);
        if (node != null) {
          doRemoveNode(node);
          header.tailKey = node.previousKey;
          header.size -= 1;
          updateHeader(header);
          return node.getElement();
        }
      }
      return null;
    } catch (IOException e) {
    	 throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
    }
   
  }

  public QueueListEntry addFirst(Object value) throws JMSException
  {
    try {
      Header header = getHeader();
      Node node = createNode();
      node.value = value;
      Long nextKey = header.headKey;
      node.nextKey = nextKey;
      Long key = createKey(header);
      node.key = key;
      updateNode(node);
      updateNextNode(nextKey, key);
      header.headKey = key;
      if (header.tailKey == null) {
        header.tailKey = key;
      }
      header.size += 1;
      updateHeader(header);
      return node;
    } catch (IOException e) {
    	throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
    }
    
  }

  public QueueListEntry addLast(Object value) throws JMSException
  {
    try {
      Header header = getHeader();
      return doAddLast(value, header);
    } catch (IOException e) {
    	throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
    }
    
  }

  public boolean contains(Object value) throws JMSException
  {
    return indexOf(value) != -1;
  }

  public int size() throws JMSException {
    try {
      return getHeader().size;
    } catch (IOException e) {
    	throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
    }
    
  }

  public boolean isEmpty() throws JMSException
  {
    int size = size();
    return size == 0;
  }

  public QueueListEntry add(Object value) throws JMSException {
    return addLast(value);
  }

  public Object get(int index) throws JMSException {
    try {
      Node node = getNode(index);
      if (node != null) {
        return node.value;
      }
      return null;
    } catch (IOException e) {
    	  throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
    }
  
  }

  public Object set(int index, Object element) throws JMSException
  {
    try {
      Node node = getNode(index);
      if (node != null) {
        Object previous = node.value;
        node.value = element;
        updateNode(node);
        return previous;
      }
      return null;
    } catch (IOException e) {
    	throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
    }
    
  }

  public void add(int index, Object element) throws JMSException
  {
    if (index == 0)
      addFirst(element);
    else
      try
      {
        Header header = getHeader();
        Node nextNode = getNode(header, index);
        doAddBefore(header, nextNode, element);
      }
      catch (IOException e) {
        throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
      }
  }

  public Object remove(int index) throws JMSException
  {
    try {
      Node node = getNode(index);
      if (node != null) {
        removeNode(node);
      }
      return null;
    } catch (IOException e) {
    	throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
    }
    
  }

  public int indexOf(Object value) throws JMSException
  {
    try {
      Header header = getHeader();
      Long key = header.headKey;
      for (int i = 0; key != null; i++) {
        Node node = getNode(key);
        if (node == null) {
          break;
        }
        if ((value == node.value) || ((value != null) && (value.equals(node.value)))) {
          return i;
        }
        key = node.nextKey;
      }
      return -1;
    } catch (IOException e) {
    	 throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
    }
   
  }

  public int lastIndexOf(Object value) throws JMSException
  {
    try {
      Header header = getHeader();
      Long key = header.tailKey;
      for (int i = header.size - 1; key != null; i--) {
        Node node = getNode(key);
        if (node == null) {
          break;
        }
        if ((value == node.value) || ((value != null) && (value.equals(node.value)))) {
          return i;
        }
        key = node.previousKey;
      }
      return -1;
    } catch (IOException e) {
    	throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
    }
    
  }

  public QueueListEntry getFirstEntry() throws JMSException
  {
    try {
      return getNode(getHeader().headKey);
    } catch (IOException e) {
    	throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
    }
    
  }

  public QueueListEntry getLastEntry() throws JMSException
  {
    try {
      return getNode(getHeader().tailKey);
    } catch (IOException e) {
    	throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
    }
    
  }

  public QueueListEntry getNextEntry(QueueListEntry entry) throws JMSException
  {
    Node node = (Node)entry;
    try {
      return getNode(node.nextKey);
    } catch (IOException e) {
    	throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
    }
    
  }

  public QueueListEntry getPrevEntry(QueueListEntry entry) throws JMSException
  {
    Node node = (Node)entry;
    try {
      return getNode(node.previousKey);
    } catch (IOException e) {
    	 throw JMSExceptionHelper.newJMSException("Failed to read from table: " + e, e);
    }
   
  }

  public QueueListEntry addBefore(Object value, QueueListEntry entry) throws JMSException
  {
    try {
      return doAddBefore(getHeader(), (Node)entry, value);
    } catch (IOException e) {
    	 throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
    }
   
  }

  public void remove(QueueListEntry entry) throws JMSException
  {
    try {
      removeNode((Node)entry);
    }
    catch (IOException e) {
      throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
    }
  }

  public Object[] toArray() throws JMSException {
    try {
      Header header = getHeader();
      int size = header.size;
      if (size == 0) {
        return EMPTY_ARRAY;
      }
      Long key = header.headKey;
      Object[] answer = new Object[size];
      for (int i = 0; (i < size) && (key != null); i++) {
        Node node = getNode(key);
        if (node != null) {
          answer[i] = node.value;
          key = node.nextKey;
        }
      }
      return answer;
    } catch (IOException e) {
    	throw JMSExceptionHelper.newJMSException("Failed to write to table: " + e, e);
    }
    
  }

  public void rotate()
    throws JMSException
  {
    Object obj = removeFirst();
    if (obj != null)
      addLast(obj);
  }

  protected Long createKey(Header header) throws IOException, JMSException
  {
    long value = header.lastKeyValue;
    while (true) {
      if (value == 9223372036854775807L) {
        value = 1L;
      }
      else {
        value += 1L;
      }
      Long key = new Long(value);
      if (getNode(key) == null) {
        header.lastKeyValue = value;
        return key;
      }
    }
  }

  protected boolean removeNode(Node node) throws IOException, JMSException {
    Header header = null;
    boolean updatedPrevious = false;
    if (node.previousKey != null)
    {
      Node previousNode = getNode(node.previousKey);
      if (previousNode != null) {
        previousNode.nextKey = node.nextKey;
        updateNode(previousNode);
        updatedPrevious = true;
      }
    }
    if (!updatedPrevious)
    {
      header = getHeader();
      header.headKey = node.nextKey;
    }

    boolean updatedNext = false;
    if (node.nextKey != null)
    {
      Node nextNode = getNode(node.nextKey);
      if (nextNode != null) {
        nextNode.previousKey = node.previousKey;
        updateNode(nextNode);
        updatedNext = true;
      }
    }
    if (!updatedNext)
    {
      header = getHeader();
      header.tailKey = node.previousKey;
    }
    return true;
  }

  protected abstract Header getHeader()
    throws IOException, JMSException;

  protected abstract void updateHeader(Header paramHeader)
    throws IOException, JMSException;

  protected abstract void updateNode(Node paramNode)
    throws IOException, JMSException;

  protected abstract Node getNode(Long paramLong)
    throws IOException, JMSException;

  protected Node getNode(int index)
    throws IOException, JMSException
  {
    Header header = getHeader();
    return getNode(header, index);
  }

  protected Node getNode(Header header, int index) throws IOException, JMSException {
    Node node = null;
    int middle = header.size / 2;
    if (index > middle)
    {
      Long key = header.tailKey;
      for (int i = header.size; (i > index) && (key != null); i--) {
        node = getNode(key);
        if (node != null)
          key = node.previousKey;
      }
    }
    else
    {
      Long key = header.headKey;
      for (int i = 0; (i <= index) && (key != null); i++) {
        node = getNode(key);
        if (node != null) {
          key = node.nextKey;
        }
      }
    }
    return node;
  }

  protected Node doAddLast(Object value, Header header) throws IOException, JMSException {
    Node node = createNode();
    Long key = createKey(header);
    node.key = key;
    node.value = value;
    Long previousKey = header.tailKey;
    node.previousKey = previousKey;
    updateNode(node);
    updatePreviousNode(previousKey, key);
    header.tailKey = key;
    if (header.headKey == null) {
      header.headKey = key;
    }
    header.size += 1;
    updateHeader(header);
    return node;
  }

  protected void updateNextNode(Long nextKey, Long key) throws IOException, JMSException {
    if (nextKey != null) {
      Node nextNode = getNode(nextKey);
      if (nextNode == null) {
        throw new IOException("Missing node for key: " + nextKey);
      }
      nextNode.previousKey = key;
      updateNode(nextNode);
    }
  }

  protected void updatePreviousNode(Long previousKey, Long key) throws IOException, JMSException {
    if (previousKey != null) {
      Node previousNode = getNode(previousKey);
      if (previousNode == null) {
        throw new IOException("Missing previous node for key: " + previousKey);
      }
      previousNode.nextKey = key;
      updateNode(previousNode);
    }
  }

  protected Node doAddBefore(Header header, Node nextNode, Object element) throws JMSException, IOException {
    if (nextNode == null) {
      return doAddLast(element, header);
    }

    Long key = createKey(header);
    Node node = createNode();
    node.value = element;
    node.key = key;
    Long previousKey = nextNode.previousKey;
    node.previousKey = previousKey;
    node.nextKey = nextNode.key;
    nextNode.previousKey = key;
    header.size += 1;

    updateNode(node);
    updateNode(nextNode);
    updatePreviousNode(previousKey, key);
    updateHeader(header);
    return node;
  }

  protected abstract void doRemoveNode(Node paramNode) throws IOException, JMSException;

  protected static Long wrapLong(long value)
  {
    if (value == 0L) {
      return null;
    }

    return new Long(value);
  }

  protected static long unwrapLong(Long key) {
    if (key != null) {
      return key.longValue();
    }

    return 0L;
  }

  protected Node createNode() {
    return new Node();
  }

  public static class Node
    implements Serializable, QueueListEntry
  {
    private static final long serialVersionUID = 4609474001468609536L;
    public Long previousKey;
    public Long nextKey;
    public Object value;
    public transient Long key;

    public Object getElement()
    {
      return this.value;
    }

    public byte[] asBytes() throws IOException {
      FastOutputStream buffer = new FastOutputStream();
      DataOutputStream out = new DataOutputStream(buffer);
      out.writeLong(QueueListSupport.unwrapLong(this.previousKey));
      out.writeLong(QueueListSupport.unwrapLong(this.nextKey));
      out.writeUTF((String)this.value);
      return buffer.toByteArray();
    }

    public void fromBytes(byte[] data) throws IOException {
      DataInputStream in = new DataInputStream(new FastInputStream(data));
      this.previousKey = QueueListSupport.wrapLong(in.readLong());
      this.nextKey = QueueListSupport.wrapLong(in.readLong());
      this.value = in.readUTF();
    }
  }

  public static class Header
    implements Serializable
  {
    private static final long serialVersionUID = 64734383295040L;
    public Long headKey;
    public Long tailKey;
    public long lastKeyValue;
    public int size;

    public byte[] asBytes()
      throws IOException
    {
      ByteArrayOutputStream buffer = new ByteArrayOutputStream();
      DataOutputStream out = new DataOutputStream(buffer);
      out.writeLong(QueueListSupport.unwrapLong(this.headKey));
      out.writeLong(QueueListSupport.unwrapLong(this.tailKey));
      out.writeLong(this.lastKeyValue);
      out.writeInt(this.size);
      return buffer.toByteArray();
    }

    public void fromBytes(byte[] data) throws IOException {
      DataInputStream in = new DataInputStream(new ByteArrayInputStream(data));
      this.headKey = QueueListSupport.wrapLong(in.readLong());
      this.tailKey = QueueListSupport.wrapLong(in.readLong());
      this.lastKeyValue = in.readLong();
      this.size = in.readInt();
    }
  }
}